{ "cells": [ { "cell_type": "markdown", "id": "9075e187-b62d-4182-bb80-2308b5d77708", "metadata": {}, "source": [ "# Matrices and Matrix Algebra\n", "\n", "*March 23, 2024*\n", "\n", "This Jupyter notebook demonstrates how to define matrices in Python and then perform some basic matrix algebra.\n", "\n", "First, we'll import numpy, which is the only package that we'll need." ] }, { "cell_type": "code", "execution_count": 1, "id": "a39dafec-19f8-45e6-88f4-aab9a23abc93", "metadata": { "tags": [] }, "outputs": [], "source": [ "import numpy as np" ] }, { "cell_type": "markdown", "id": "b1daa991-96f7-4a9d-ae7a-bb14a46c2224", "metadata": {}, "source": [ "Here's how you can enter, for example, a 3x4 matrix. There are 3 rows of 4 elelments each (i.e. 4 columns)." ] }, { "cell_type": "code", "execution_count": 4, "id": "incident-courage", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "[[1, 3, 0, 4], [2, -2, 1, 2], [-4, 1, -1, -7]]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = [[1, 3, 0, 4], [2, -2, 1, 2], [-4, 1, -1, -7]]\n", "m" ] }, { "cell_type": "markdown", "id": "90bb8803-9c11-4e8a-a151-949ac8243ff2", "metadata": {}, "source": [ "The output of the matrix doesn't look very nice. We can use numpy to improve the look of the matrix output." ] }, { "cell_type": "code", "execution_count": 8, "id": "6fad4fa2-2d7b-4f66-a9d8-26a03c351155", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 1 3 0 4]\n", " [ 2 -2 1 2]\n", " [-4 1 -1 -7]]\n" ] } ], "source": [ "M = np.matrix(m)\n", "print(M)" ] }, { "cell_type": "markdown", "id": "69cc9b72-c666-4125-9846-778606e7e90d", "metadata": {}, "source": [ "For what it's worth, I found on Stack Overflow the following only-line bit of code for formatting the matrix output in another way." ] }, { "cell_type": "code", "execution_count": 9, "id": "4a50f25d-6886-4335-964c-fca021632248", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\t3\t0\t4\n", "2\t-2\t1\t2\n", "-4\t1\t-1\t-7\n" ] } ], "source": [ "print('\\n'.join(['\\t'.join([str(cell) for cell in row]) for row in m]))" ] }, { "cell_type": "markdown", "id": "8745a7dc-d6a7-49af-b29c-38f11a7fe632", "metadata": {}, "source": [ "We can also get the shape of out matrix using numpy." ] }, { "cell_type": "code", "execution_count": 12, "id": "filled-idaho", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(3, 4)\n", "Matrix M has 3 rows 4 columns and 12 elements.\n" ] } ], "source": [ "s = np.shape(M)\n", "print(s)\n", "print('Matrix M has', s[0], 'rows', s[1], 'columns and', np.size(m), 'elements.')" ] }, { "cell_type": "markdown", "id": "8e2b74c2-ec54-4301-b9dc-3b0671a46823", "metadata": {}, "source": [ "Another option for inputting a matrix is to first start with a $n\\times m$ matrix of zeros and then assign specific values to the various elements. Below, we load a $3\\times 3$ matrix of zeros." ] }, { "cell_type": "code", "execution_count": 30, "id": "choice-benjamin", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[0. 0. 0.]\n", " [0. 0. 0.]\n", " [0. 0. 0.]]\n" ] } ], "source": [ "# Make a 3x3 matrix of zeros\n", "M1 = np.matrix(np.zeros((3,3)))\n", "print(M1)" ] }, { "cell_type": "markdown", "id": "149b754e-fe12-4295-8880-eb8620fbfe89", "metadata": {}, "source": [ "We can then index M1 to assign values to the various elements. Remember, Python starts counting from zero, so what we would refer to as the (1, 1) element will be accessed using ```M1[0, 0]```. Let's make M1 a symmetric matrix." ] }, { "cell_type": "code", "execution_count": 31, "id": "b7f7db0b-6c7b-4fda-b95b-629481991643", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[1. 3. 5.]\n", " [3. 4. 7.]\n", " [5. 7. 8.]]\n" ] } ], "source": [ "M1[0, 0] = 1\n", "M1[1, 1] = 4\n", "M1[2, 2] = 8\n", "M1[0, 1] = 3\n", "M1[1, 0] = M1[0, 1]\n", "M1[0, 2] = 5\n", "M1[2, 0] = M1[0, 2]\n", "M1[1, 2] = 7\n", "M1[2, 1] = M1[1, 2]\n", "\n", "print(M1)" ] }, { "cell_type": "markdown", "id": "af2c8b69-52be-4f20-8131-fc060d8d67d5", "metadata": {}, "source": [ "Of course, we can also select any element of a matrix using the same indexing method." ] }, { "cell_type": "code", "execution_count": 32, "id": "36178564-d331-4d90-b64a-93d55dde2826", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "7.0" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M1[1, 2]" ] }, { "cell_type": "markdown", "id": "521f6024-5e0d-42df-8c9f-392a58ba66af", "metadata": {}, "source": [ "We can also select entire row are columns as follows." ] }, { "cell_type": "code", "execution_count": 36, "id": "leading-triple", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "row 2: [[3. 4. 7.]] \n", "\n", "row 2: [[3. 4. 7.]] \n", "\n", "col 3:\n", " [[5.]\n", " [7.]\n", " [8.]]\n" ] } ], "source": [ "# Here's entire 2nd row.\n", "row2 = M1[1]\n", "print('row 2:', row2, '\\n')\n", "\n", "# Here's another way to get the 2nd row.\n", "row2 = M1[1, :]\n", "print('row 2:', row2,'\\n')\n", "\n", "# Here's the third column\n", "col3 = M1[:, 2]\n", "print('col 3:\\n', col3)" ] }, { "cell_type": "markdown", "id": "0c1e8008-8e86-44ea-b11d-8e7bda3a5d61", "metadata": {}, "source": [ "We can assign new values to an entire row." ] }, { "cell_type": "code", "execution_count": 37, "id": "noted-charleston", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 1. 3. 5.]\n", " [10. 20. 30.]\n", " [ 5. 7. 8.]]\n" ] } ], "source": [ "M1[1, :] = np.arange(10, 40, 10)\n", "print(M1)" ] }, { "cell_type": "markdown", "id": "0f39f6d4-a64a-4926-bb8f-c0a1236a038f", "metadata": {}, "source": [ "We can also assign new values to an entire column." ] }, { "cell_type": "code", "execution_count": 38, "id": "intelligent-signature", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 1. 3. -5.]\n", " [ 10. 20. -10.]\n", " [ 5. 7. -15.]]\n" ] } ], "source": [ "M1[:, 2] = np.transpose([np.arange(-5, -20, -5)])\n", "print(M1)" ] }, { "cell_type": "markdown", "id": "3658fd73-91d5-4e89-89d4-2aacbf78eb56", "metadata": {}, "source": [ "Let's now recall our $3\\times 4$ matrix M and define a new $4\\times 3$ matrix called N." ] }, { "cell_type": "code", "execution_count": 45, "id": "fitting-child", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "M:\n", " [[ 1 3 0 4]\n", " [ 2 -2 1 2]\n", " [-4 1 -1 -7]]\n", "\n", "N:\n", " [[ 1 2 3]\n", " [ 4 5 6]\n", " [ 7 8 9]\n", " [10 11 12]]\n" ] } ], "source": [ "print('M:\\n', M)\n", "\n", "N = np.matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])\n", "print('\\nN:\\n', N)" ] }, { "cell_type": "markdown", "id": "f759690c-0e60-4369-a89e-e7611ec14213", "metadata": {}, "source": [ "We can multiply our $3\\times 4$ and $4\\times 3$ matrices to create a new $3\\times 3$ matrix. We'll use ```np.dot(M, N)``` to do the matrix multiplication $\\underline{\\underline{P}} = \\underline{\\underline{M}}\\,\\underline{\\underline{N}}$." ] }, { "cell_type": "code", "execution_count": 46, "id": "pointed-parker", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 53 61 69]\n", " [ 21 24 27]\n", " [-77 -88 -99]]\n" ] } ], "source": [ "P = np.dot(M, N)\n", "print(P)" ] }, { "cell_type": "markdown", "id": "5c1697af-7aca-4d42-ab52-05a188afa8f4", "metadata": {}, "source": [ "We can also multiply our $4\\times 3$ and $3\\times 4$ matrices to create a new $4\\times 4$ matrix. Numpy has another (equivalent) way to do matrix multiplication: ```np.matmul(M, N)```. Here is the result of $\\underline{\\underline{Q}} = \\underline{\\underline{N}}\\,\\underline{\\underline{M}}$." ] }, { "cell_type": "code", "execution_count": 47, "id": "overall-printer", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ -7 2 -1 -13]\n", " [-10 8 -1 -16]\n", " [-13 14 -1 -19]\n", " [-16 20 -1 -22]]\n" ] } ], "source": [ "Q = np.matmul(N, M)\n", "print(Q)" ] }, { "cell_type": "markdown", "id": "d28428a3-699f-4487-8b0f-6a52191f5959", "metadata": {}, "source": [ "Note that you can also do element-by-element multiplication of two of matrices using ```np.multiply()```. Here's an example." ] }, { "cell_type": "code", "execution_count": 50, "id": "smaller-vaccine", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 10 40 90]\n", " [160 250 360]]\n" ] } ], "source": [ "x = np.matrix([[1, 2, 3], [4, 5, 6]])\n", "y = np.matrix([[10, 20, 30], [40, 50, 60]])\n", "z = np.multiply(x, y)\n", "\n", "print(z)" ] }, { "cell_type": "markdown", "id": "470cd071-edcd-4407-99aa-4d8dcb916ff8", "metadata": {}, "source": [ "Here's some code that I found online that generates a magic square (all rows and columns add to the same number). I don't claim to understand the code." ] }, { "cell_type": "code", "execution_count": 73, "id": "thick-happening", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[11.5 13.5 20.5 27.5 4.5]\n", " [17.5 19.5 26.5 3.5 10.5]\n", " [18.5 25.5 7.5 9.5 16.5]\n", " [24.5 6.5 8.5 15.5 22.5]\n", " [ 5.5 12.5 14.5 21.5 23.5]]\n" ] } ], "source": [ "n = 5\n", "M5 = np.matrix([[(i+j-1+n/2)%n*n+(i+2*j-2)%n+1for j in range(n)]for i in range(n)])\n", "print(M5)" ] }, { "cell_type": "markdown", "id": "16649db3-3b0f-4c42-96c4-900a9aa2859a", "metadata": {}, "source": [ "Let's check if the square is indeed magic by summing all rows and columns." ] }, { "cell_type": "code", "execution_count": 74, "id": "female-restaurant", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Row 0 sum is 77.5\n", "Col 0 sum is 77.5\n", "Row 1 sum is 77.5\n", "Col 1 sum is 77.5\n", "Row 2 sum is 77.5\n", "Col 2 sum is 77.5\n", "Row 3 sum is 77.5\n", "Col 3 sum is 77.5\n", "Row 4 sum is 77.5\n", "Col 4 sum is 77.5\n" ] } ], "source": [ "for i in range(n):\n", " print('Row', i, 'sum is', M5[i].sum())\n", " print('Col', i, 'sum is', M5[:, i].sum())" ] }, { "cell_type": "markdown", "id": "c0ddb839-ac03-4a00-842c-b95b45fd2c2f", "metadata": {}, "source": [ "Let's check the sum along the diagonal (top-left to bottom-right, called the principle diagonal). We can access the diagonal elements using ```np.diagonal()```." ] }, { "cell_type": "code", "execution_count": 75, "id": "bb3122ce-c98d-4e82-85c3-77f9cafc8c84", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The diagonal elements of M5 are: [11.5 19.5 7.5 15.5 23.5]\n", "The sum of the principle diagonal elements is: 77.5\n" ] } ], "source": [ "dia = np.diagonal(M5)\n", "dia_sum = sum(np.diagonal(M5))\n", "print('The diagonal elements of M5 are:', dia)\n", "print('The sum of the principle diagonal elements is:', dia_sum)" ] }, { "cell_type": "markdown", "id": "4bd1058e-507d-4314-b8e6-91aef66c56e2", "metadata": {}, "source": [ "The principle diagonal has the correct sum. What about the diagonal from top-right to bottom-left, called the secondary diagonal?\n", "\n", "We can first use ```np.fliplr()``` to reverse the order of each row. Notice that this makes the secondary diagonal of M5 become the principle diagonal of the \"flipped\" matrix." ] }, { "cell_type": "code", "execution_count": 76, "id": "0facc689-820b-455f-b625-b64693d93712", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 4.5 27.5 20.5 13.5 11.5]\n", " [10.5 3.5 26.5 19.5 17.5]\n", " [16.5 9.5 7.5 25.5 18.5]\n", " [22.5 15.5 8.5 6.5 24.5]\n", " [23.5 21.5 14.5 12.5 5.5]]\n" ] } ], "source": [ "M5_flipped = np.fliplr(M5)\n", "print(M5_flipped)" ] }, { "cell_type": "markdown", "id": "2a96cd6a-8282-4dff-9302-0a504ac6b037", "metadata": {}, "source": [ "As the result below shows, the secondary diagonal does **not** have the correct sum. Therefore, the matrix M5 is *not* a true Magic Square. When one or both of the diagonals do not have the right sum, the square is referred to as a *semimagic square*." ] }, { "cell_type": "code", "execution_count": 77, "id": "a3b70a56-8e07-47f0-91b5-a8d8b7b5a30a", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The secondary diagonal elements of M5 are: [4.5 3.5 7.5 6.5 5.5]\n", "The sum of the secondary diagonal elements is: 27.5\n" ] } ], "source": [ "dia = np.diagonal(M5_flipped)\n", "dia_sum = sum(np.diagonal(M5_flipped))\n", "print('The secondary diagonal elements of M5 are:', dia)\n", "print('The sum of the secondary diagonal elements is:', dia_sum)" ] }, { "cell_type": "markdown", "id": "ae90f8b8-148d-4cc9-bd10-a4a5b13fe581", "metadata": {}, "source": [ "Let's find the inverse of our $5\\times 5$ semimagic square. The relevant command is ```np.linalg.inv()```." ] }, { "cell_type": "code", "execution_count": 80, "id": "representative-tulsa", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 0.01059347 0.00226013 0.00450372 0.04104218 -0.04549628]\n", " [ 0.00290116 -0.00543218 0.05065757 -0.03588089 0.00065757]\n", " [ 0.00097808 0.04264475 -0.03780397 -0.00511166 0.01219603]\n", " [ 0.03591398 -0.03075269 0.00258065 0.00258065 0.00258065]\n", " [-0.03748346 0.00418321 -0.00703474 0.01027295 0.04296526]]\n" ] } ], "source": [ "iM5 = np.linalg.inv(M5)\n", "print(iM5)" ] }, { "cell_type": "markdown", "id": "e5f740d3-baca-4533-a23d-cd995977aee0", "metadata": {}, "source": [ "We can multiply M5 by its inverse to check if it produces the identity matrix." ] }, { "cell_type": "code", "execution_count": 114, "id": "scheduled-specialist", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1.0\t0.0\t0.0\t0.0\t0.0\n", "0.0\t1.0\t0.0\t0.0\t0.0\n", "0.0\t0.0\t1.0\t0.0\t0.0\n", "0.0\t0.0\t0.0\t1.0\t0.0\n", "0.0\t0.0\t0.0\t0.0\t1.0\n" ] } ], "source": [ "# Do the matrix multiplication\n", "I = np.dot(iM5, M5)\n", "\n", "# Make an empty 5 x 5 matrix\n", "Irnd = np.zeros((5, 5))\n", "\n", "# Loop over the elements and round the values to the neareast tenth \n", "for i in range(5):\n", " for j in range(5):\n", " Irnd[i, j] = round(I[i, j])\n", " \n", "# Diplay the result in a nice format\n", "print('\\n'.join(['\\t'.join([str(cell) for cell in row]) for row in Irnd]))" ] }, { "cell_type": "markdown", "id": "24b3cb03-789e-4225-a84a-32f4cfa3c2c7", "metadata": {}, "source": [ "It's also easy to take the transpose of matrices. Of course, the transpose of a (semi)magic matrix is still (semi)magic! " ] }, { "cell_type": "code", "execution_count": 115, "id": "considered-integral", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[11.5 17.5 18.5 24.5 5.5]\n", " [13.5 19.5 25.5 6.5 12.5]\n", " [20.5 26.5 7.5 8.5 14.5]\n", " [27.5 3.5 9.5 15.5 21.5]\n", " [ 4.5 10.5 16.5 22.5 23.5]]\n" ] } ], "source": [ "M5T = np.transpose(M5)\n", "print(M5T)" ] }, { "cell_type": "markdown", "id": "ab3f5dc7-d2f1-4a21-9123-d3577ca725f4", "metadata": {}, "source": [ "Someone in a quantum mechanics course may want to take the Hermitian conjugate of a matrix. First, we create a matrix with complex-valued elements." ] }, { "cell_type": "code", "execution_count": 116, "id": "extensive-immigration", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 0.+1.j 0.+3.j 4.+0.j]\n", " [ 2.+3.j -2.+0.j 1.-2.j]]\n" ] } ], "source": [ "M2 = np.matrix([[1j, 3j, 4], [2 + 3j, -2, 1 - 2j]])\n", "print(M2)" ] }, { "cell_type": "markdown", "id": "633fb309-2ed4-481c-9092-47679573fe12", "metadata": {}, "source": [ "Here's the Hermitian conjugate." ] }, { "cell_type": "code", "execution_count": 117, "id": "institutional-palestine", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 0.-1.j 2.-3.j]\n", " [ 0.-3.j -2.-0.j]\n", " [ 4.-0.j 1.+2.j]]\n" ] } ], "source": [ "M2_hc = M2.getH()\n", "print(M2_hc)" ] }, { "cell_type": "markdown", "id": "3ed8bf7d-e32c-498c-92a0-a7025b93d6ed", "metadata": {}, "source": [ "Of course, we can also find the Hermitian conjugate by first evaluating the conjugate and then taking the transpose (or in the same steps in the opposite order)." ] }, { "cell_type": "code", "execution_count": 118, "id": "demographic-substitute", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 0.-1.j 2.-3.j]\n", " [ 0.-3.j -2.-0.j]\n", " [ 4.-0.j 1.+2.j]]\n" ] } ], "source": [ "M2_hc = np.conjugate(np.transpose(M2))\n", "print(M2_hc)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.8" } }, "nbformat": 4, "nbformat_minor": 5 }